//
// Created by zhouzechao on 2019-09-17.
//
#include <unistd.h>
#include <jni.h>
#include <AndroidLog.h>
#include <string.h>
#include "MetadataRetriever.h"
#include "Errors.h"

struct retriever_fields_t {
    jfieldID context;
};

static retriever_fields_t fields;

static int register_Metadata(JNIEnv *pEnv);

static void MediaMetadataRetriever_setDataSource(JNIEnv *env, jobject thiz, jstring path_);

static void
MediaMetadataRetriever_setDataSourceAndHeaders(JNIEnv *env, jobject thiz, jstring path_,
                                               jobjectArray keys, jobjectArray values);

static void
MediaMetadataRetriever_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor,
                                       jlong offset, jlong length);

static jbyteArray
MediaMetadataRetriever_getFrameAtTime(JNIEnv *env, jobject thiz, jlong timeUs, jint option);

static jbyteArray
MediaMetadataRetriever_getScaleFrameAtTime(JNIEnv *env, jobject thiz, jlong timeUs, jint option,
                                           jint width, jint height);

static jbyteArray
MediaMetadataRetriever_getEmbeddedPicture(JNIEnv *env, jobject thiz, jint pictureType);

static jstring MediaMetadataRetriever_extractMetadata(JNIEnv *env, jobject thiz, jstring keyCode);

static jstring
MediaMetadataRetriever_extractMetadataFromChapter(JNIEnv *env, jobject thiz, jstring keyCode,
                                                  jint chapter);

static jobject MediaMetadataRetriever_getAllMetadata(JNIEnv *env, jobject thiz);

static void MediaMetadataRetriever_release(JNIEnv *env, jobject thiz);

static void MediaMetadataRetriever_setup(JNIEnv *env, jobject thiz);

static void MediaMetadataRetriever_native_init(JNIEnv *env, jobject thiz);

static void MediaMetadataRetriever_native_finalize(JNIEnv *env, jobject thiz);

static MetadataRetriever *getRetriever(JNIEnv *env, jobject thiz);

static void throwException(JNIEnv *pEnv, const char *className, const char *msg);

static void
process_media_retriever_call(JNIEnv *env, status_t opStatus, const char *exception,
                             const char *message);

extern "C" {
#include <libavcodec/jni.h>
}

static const char *const RETRIEVER_CLASS_NAME = "com/lib/media/KKMetadataRetriever";

static JNINativeMethod nativeMethods[] = {
        {"setDataSource",              "(Ljava/lang/String;)V",                   (void *) MediaMetadataRetriever_setDataSource},
        {"_setDataSource",             "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",
                                                                                  (void *) MediaMetadataRetriever_setDataSourceAndHeaders},
        {"setDataSource",              "(Ljava/io/FileDescriptor;JJ)V",           (void *) MediaMetadataRetriever_setDataSourceFD},
        {"_getFrameAtTime",            "(JI)[B",                                  (void *) MediaMetadataRetriever_getFrameAtTime},
        {"_getScaledFrameAtTime",      "(JIII)[B",                                (void *) MediaMetadataRetriever_getScaleFrameAtTime},
        {"getEmbeddedPicture",         "(I)[B",                                   (void *) MediaMetadataRetriever_getEmbeddedPicture},
        {"extractMetadata",            "(Ljava/lang/String;)Ljava/lang/String;",  (void *) MediaMetadataRetriever_extractMetadata},
        {"extractMetadataFromChapter", "(Ljava/lang/String;I)Ljava/lang/String;", (void *) MediaMetadataRetriever_extractMetadataFromChapter},
        {"_getAllMetadata",            "()Ljava/util/HashMap;",                   (void *) MediaMetadataRetriever_getAllMetadata},
        {"release",                    "()V",                                     (void *) MediaMetadataRetriever_release},
        {"native_setup",               "()V",                                     (void *) MediaMetadataRetriever_setup},
        {"native_init",                "()V",                                     (void *) MediaMetadataRetriever_native_init},
        {"native_finalize",            "()V",                                     (void *) MediaMetadataRetriever_native_finalize},

};

extern "C"
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    av_jni_set_java_vm(vm, NULL);
    JNIEnv *env;
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
        return -1;
    }
    if (register_Metadata(env) != JNI_OK) {
        return -1;
    }
    return JNI_VERSION_1_4;
}


// 注册CainMediaMetadataRetriever的Native方法
static int register_Metadata(JNIEnv *env) {
    int numMethods = (sizeof(nativeMethods) / sizeof((nativeMethods)[0]));
    jclass clazz = env->FindClass(RETRIEVER_CLASS_NAME);
    if (clazz == NULL) {
        ALOGE("Native registration unable to find class '%s'", RETRIEVER_CLASS_NAME);
        return JNI_ERR;
    }
    if (env->RegisterNatives(clazz, nativeMethods, numMethods) < 0) {
        ALOGE("Native registration unable to find class '%s'", RETRIEVER_CLASS_NAME);
        return JNI_ERR;
    }
    env->DeleteLocalRef(clazz);

    return JNI_OK;
}

static void
MediaMetadataRetriever_setDataSource(JNIEnv *env, jobject thiz, jstring path_) {
    MediaMetadataRetriever_setDataSourceAndHeaders(env, thiz, path_, NULL, NULL);
}

static void
MediaMetadataRetriever_setDataSourceAndHeaders(JNIEnv *env, jobject thiz, jstring path_,
                                               jobjectArray keys, jobjectArray values) {
    ALOGV("MediaMetadataRetriever_setDataSourceAndHeaders");
    MetadataRetriever *retriever = getRetriever(env, thiz);
    if (retriever == NULL) {
        throwException(env, "java/lang/IllegalStateException", "No retriever available");
        return;
    }

    if (!path_) {
        throwException(env, "java/lang/IllegalArgumentException", "Null pointer");
        return;
    }

    const char *path = env->GetStringUTFChars(path_, NULL);
    if (!path) {
        return;
    }

    if (strncmp("mem://", path, 6) == 0) {
        throwException(env, "java/lang/IllegalArgumentException", "Invalid pathname");
        return;
    }

    const char *restrict = strstr(path, "mms://");
    char *restrict_to = restrict ? strdup(restrict) : NULL;
    if (restrict_to != NULL) {
        strncpy(restrict_to, "mmsh://", 6);
        puts(path);
    }

    char *headers = NULL;
    if (keys && values != NULL) {
        int keysCount = env->GetArrayLength(keys);
        int valuesCount = env->GetArrayLength(values);

        if (keysCount != valuesCount) {
            ALOGE("keys and values arrays have different length");
            throwException(env, "java/lang/IllegalArgumentException", NULL);
            return;
        }

        int i = 0;
        const char *rawString = NULL;
        char hdrs[2048];

        for (i = 0; i < keysCount; i++) {
            jstring key = (jstring) env->GetObjectArrayElement(keys, i);
            rawString = env->GetStringUTFChars(key, NULL);
            strcat(hdrs, rawString);
            strcat(hdrs, ": ");
            env->ReleaseStringUTFChars(key, rawString);

            jstring value = (jstring) env->GetObjectArrayElement(values, i);
            rawString = env->GetStringUTFChars(value, NULL);
            strcat(hdrs, rawString);
            strcat(hdrs, "\r\n");
            env->ReleaseStringUTFChars(value, rawString);
        }

        headers = &hdrs[0];
    }

    status_t opStatus = retriever->setDataSource(path, 0, headers);
    process_media_retriever_call(env, opStatus, "java/io/IOException", "setDataSource failed.");

    env->ReleaseStringUTFChars(path_, path);
}


static void
MediaMetadataRetriever_setDataSourceFD(JNIEnv *evn, jobject thiz, jobject fileDescriptor,
                                       jlong offset, jlong length) {

}

static jbyteArray
MediaMetadataRetriever_getFrameAtTime(JNIEnv *evn, jobject thiz, jlong timeUs, jint option) {

}

static jbyteArray
MediaMetadataRetriever_getScaleFrameAtTime(JNIEnv *evn, jobject thiz, jlong timeUs, jint option,
                                           jint width, jint height) {

}

static jbyteArray
MediaMetadataRetriever_getEmbeddedPicture(JNIEnv *evn, jobject thiz, jint pictureType) {

}

static jstring MediaMetadataRetriever_extractMetadata(JNIEnv *evn, jobject thiz, jstring keyCode) {

}

static jstring
MediaMetadataRetriever_extractMetadataFromChapter(JNIEnv *evn, jobject thiz, jstring keyCode,
                                                  jint chapter) {

}

static jobject MediaMetadataRetriever_getAllMetadata(JNIEnv *evn, jobject thiz) {

}

static void MediaMetadataRetriever_release(JNIEnv *evn, jobject thiz) {

}

static void MediaMetadataRetriever_setup(JNIEnv *evn, jobject thiz) {

}

static void MediaMetadataRetriever_native_init(JNIEnv *evn, jobject thiz) {

}

static void MediaMetadataRetriever_native_finalize(JNIEnv *evn, jobject thiz) {

}

// ========================
static void throwException(JNIEnv *env, const char *className, const char *msg) {
    jclass exception = env->FindClass(className);
    env->ThrowNew(exception, msg);
}

static void
process_media_retriever_call(JNIEnv *env, status_t opStatus, const char *exception,
                             const char *message) {
    if (opStatus == (status_t) INVALID_OPERATION) {
        throwException(env, "java/lang/IllegalStateException", NULL);
    } else if (opStatus != (status_t) OK) {
        if (strlen(message) > 230) {
            throwException(env, exception, message);
        } else {
            char msg[256];
            sprintf(msg, "%s: status = 0x%X", message, opStatus);
            throwException(env, exception, msg);
        }
    }
}

static MetadataRetriever *getRetriever(JNIEnv *env, jobject thiz) {
    MetadataRetriever *retriever = (MetadataRetriever *) env->GetLongField(thiz, fields.context);
    return retriever;
}